import { ReactiveProperty } from './../libs/rxjs/cc3/ReactiveProperty';
import { sys, error } from 'cc';
import { _decorator, color, director, System } from 'cc';
import { BaseManager } from '../../scripts/libs/rxjs/cc3/BaseManager';
import { native } from 'cc';
import { Observable } from '../libs/rxjs/internal/Observable';
import { NATIVE } from 'cc/env';
import { EMPTY, of } from 'rxjs';
import { SubgameConfig } from './SubgameConfig';
const { ccclass, property } = _decorator;

export enum HasUpdateStatus {
    Unknown, // 未知
    Checked, // 已检查
    UpToDate, // 已是最新(无更新)
    HasUpdate, // 有更新
}

export type HotUpdateStatus = {
    status: HasUpdateStatus;
    localVersion: string;
    remoteVersion: string;
    percent: number;
}

@ccclass('HotUpdateManager')
export class HotUpdateManager extends BaseManager {

    public static instance: HotUpdateManager;
    static ID = 'hotupdate_system';
    get logColorOverride() { return color().fromHEX('#EECCDD') };

    static registerSystem() {
        director.unregisterSystem(director.getSystem(HotUpdateManager.ID));
        director.registerSystem(HotUpdateManager.ID, hum, System.Priority.MEDIUM);
        hum.init();
    }

    static unregisterSystem() {
        hum.destroy();
        director.unregisterSystem(director.getSystem(HotUpdateManager.ID));
    }

    onInitManager() {
        this.log('onInitManager', this.timestamp);
        this.initBundleStatusRecord();
    }

    useObserves() {
        return [
        ];
    }

    getStoragePath(bundleName: string = null) {
        if (!bundleName) {
            return native.fileUtils.getWritablePath() + 'blackjack-remote-asset';
        } else {
            return native.fileUtils.getWritablePath() + `blackjack-remote-asset/subgames/assets/${bundleName}`;
        }
    }

    getSubgamePath() {
        return native.fileUtils.getWritablePath() + `blackjack-remote-asset/subgames/`;
    }

    versionCompareHandle = function (versionA: string, versionB: string) {
        this.log("Compare: version A is " + versionA + ', version B is ' + versionB);
        var vA = versionA.split('.');
        var vB = versionB.split('.');
        for (var i = 0; i < vA.length; ++i) {
            var a = parseInt(vA[i]);
            var b = parseInt(vB[i] || '0');
            if (a === b) {
                continue;
            } else {
                return a - b;
            }
        }
        if (vB.length > vA.length) {
            return -1;
        } else {
            return 0;
        }
    };

    static StateToString(state: native.AssetsManager.State) {
        switch (state) {
            case native.AssetsManager.State.UNINITED: return 'UNINITED';
            case native.AssetsManager.State.UNCHECKED: return 'UNCHECKED';
            case native.AssetsManager.State.PREDOWNLOAD_VERSION: return 'PREDOWNLOAD_VERSION';
            case native.AssetsManager.State.DOWNLOADING_VERSION: return 'DOWNLOADING_VERSION';
            case native.AssetsManager.State.VERSION_LOADED: return 'VERSION_LOADED';
            case native.AssetsManager.State.PREDOWNLOAD_MANIFEST: return 'PREDOWNLOAD_MANIFEST';
            case native.AssetsManager.State.DOWNLOADING_MANIFEST: return 'DOWNLOADING_MANIFEST';
            case native.AssetsManager.State.MANIFEST_LOADED: return 'MANIFEST_LOADED';
            case native.AssetsManager.State.NEED_UPDATE: return 'NEED_UPDATE';
            case native.AssetsManager.State.READY_TO_UPDATE: return 'READY_TO_UPDATE';
            case native.AssetsManager.State.UPDATING: return 'UPDATING';
            case native.AssetsManager.State.UNZIPPING: return 'UNZIPPING';
            case native.AssetsManager.State.UP_TO_DATE: return 'UP_TO_DATE';
            case native.AssetsManager.State.FAIL_TO_UPDATE: return 'FAIL_TO_UPDATE';
            default: return '';
        }
    }

    static EventCodeToString(eventCode: number) {
        switch (eventCode) {
            case native.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: return 'ERROR_NO_LOCAL_MANIFEST';
            case native.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: return 'ERROR_DOWNLOAD_MANIFEST';
            case native.EventAssetsManager.ERROR_PARSE_MANIFEST: return 'ERROR_PARSE_MANIFEST';
            case native.EventAssetsManager.NEW_VERSION_FOUND: return 'NEW_VERSION_FOUND';
            case native.EventAssetsManager.ALREADY_UP_TO_DATE: return 'ALREADY_UP_TO_DATE';
            case native.EventAssetsManager.UPDATE_PROGRESSION: return 'UPDATE_PROGRESSION';
            case native.EventAssetsManager.ASSET_UPDATED: return 'ASSET_UPDATED';
            case native.EventAssetsManager.ERROR_UPDATING: return 'ERROR_UPDATING';
            case native.EventAssetsManager.UPDATE_FINISHED: return 'UPDATE_FINISHED';
            case native.EventAssetsManager.UPDATE_FAILED: return 'UPDATE_FAILED';
            case native.EventAssetsManager.ERROR_DECOMPRESS: return 'ERROR_DECOMPRESS';
            default: return 'UNKNOWN_EVENT';
        }
    }

    getLobbyManifestStr(fileName) {
        let lobbyManifestFilePath1 = `${this.getStoragePath()}/${fileName}`;
        let lobbyManifestFilePath2 = `@assets/${fileName}`;
        if (native.fileUtils.isFileExist(lobbyManifestFilePath1)) {
            this.log('lobbyManifestFilePath1 file exists:', lobbyManifestFilePath1);
            return native.fileUtils.getStringFromFile(lobbyManifestFilePath1);
        } else if (native.fileUtils.isFileExist(lobbyManifestFilePath2)) {
            this.log('lobbyManifestFilePath2 file exists:', lobbyManifestFilePath2);
            let str = native.fileUtils.getStringFromFile(lobbyManifestFilePath2);
            // 兼容大厅也不在包里的情况，但是前提要把大厅的热更过程放在进入大厅之前，比如LaunchScene里面进行
            if (!native.fileUtils.isFileExist("@assets/assets/ab_lobby/cc.config.json")) { // 通过判断cc.config.json文件是否存在，来认为包内是否有大厅，没有的话同样需要走热更流程（把当前版本号设定为0即可）
                let obj = JSON.stringify(str);
                obj['version'] = '0.0.0.0';
                str = JSON.stringify(obj);
            }
            return str;
        } else {
            this.error('lobbyManifestFilePath1 and lobbyManifestFilePath2 not exists:', lobbyManifestFilePath1, lobbyManifestFilePath2);
        }
    }

    /**
     * http://192.168.31.86:8080/Game102/hotupdate/android/p1/remote-assets/
     * http://192.168.31.86:8080/Game102/hotupdate/android/p1/subgame-remote-assets/ab_subgame001/
     */
    getSubGameUrlFromLobbyUrl(lobbyUrl: string, bundleName: string) {
        let subGameUrl = lobbyUrl.replace('remote-assets', 'subgame-remote-assets');
        return `${subGameUrl}${bundleName}/`;
    }

    startProcess(bundleKey: string = null, justCheck = false): Observable<{ event: native.EventAssetsManager }> {
        let bundleName = SubgameConfig?.[bundleKey]?.assetbundleName;
        this.log('@startProcess', bundleName, justCheck);
        this.bundleStatusRecord[bundleKey].value.status = HasUpdateStatus.Checked;
        this.bundleStatusRecord[bundleKey].again();
        if (!NATIVE) return EMPTY;
        return EMPTY;
        return new Observable(observer => {
            let fileName = justCheck ? 'version.manifest' : 'project.manifest';
            let manifest = null;
            let manifestStr = null;
            if (bundleName == null) {
                manifestStr = this.getLobbyManifestStr(fileName);
                manifest = new native.Manifest(manifestStr, this.getStoragePath());
            } else {
                let lobbyManifestFilePath1 = `${this.getStoragePath(bundleName)}/${fileName}`; //native.fileUtils.getWritablePath() + `blackjack-remote-asset/${fileName}`;
                if (native.fileUtils.isFileExist(lobbyManifestFilePath1)) {
                    this.log('lobbyManifestFilePath1 file exists:', lobbyManifestFilePath1);
                    manifestStr = native.fileUtils.getStringFromFile(lobbyManifestFilePath1);
                    manifest = new native.Manifest(manifestStr, this.getStoragePath(bundleName));
                } else {
                    let lobbyManifestStr = this.getLobbyManifestStr('version.manifest');
                    this.log('lobbyManifestStr:', lobbyManifestStr);
                    let lobbyManifest = JSON.parse(lobbyManifestStr);
                    let packageUrl = this.getSubGameUrlFromLobbyUrl(lobbyManifest.packageUrl, bundleName);
                    let remoteManifestUrl = `${packageUrl}${bundleName}_project.manifest`;
                    let remoteVersionUrl = `${packageUrl}${bundleName}_version.manifest`;
                    this.log('subgameUrl:', packageUrl);
                    this.log('remoteManifestUrl:', remoteManifestUrl);
                    this.log('remoteVersionUrl:', remoteVersionUrl);
                    let customManifestStr = JSON.stringify({
                        packageUrl,
                        remoteManifestUrl,
                        remoteVersionUrl,
                        version: '0.0.0.0'
                    });
                    manifest = new native.Manifest(customManifestStr, this.getStoragePath(bundleName));
                }
            }
            this.log('@@manifest:', manifestStr, manifest, this.getStoragePath(bundleName));
            let am = native.AssetsManager.create('', this.getStoragePath(bundleName));
            am.loadLocalManifest(manifest, this.getStoragePath(bundleName));
            am.setVersionCompareHandle(this.versionCompareHandle.bind(this));

            this.log('@@localVersion:', am.getLocalManifest().getVersion());
            this.bundleStatusRecord[bundleKey].value.localVersion = am.getLocalManifest().getVersion();
            this.bundleStatusRecord[bundleKey].again();
            let completed = false;
            am.setEventCallback((event: native.EventAssetsManager) => {
                let eventCode = event.getEventCode();
                let state = event.getAssetsManagerEx().getState();
                this.log('@event: @state', state, bundleName, HotUpdateManager.StateToString(state));
                switch (eventCode) {
                    case native.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.ERROR_PARSE_MANIFEST:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.NEW_VERSION_FOUND:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode, am.getRemoteManifest().getVersion());
                        this.bundleStatusRecord[bundleKey].value.remoteVersion = am.getRemoteManifest().getVersion();
                        this.bundleStatusRecord[bundleKey].value.status = HasUpdateStatus.HasUpdate;
                        this.bundleStatusRecord[bundleKey].again();
                        break;
                    case native.EventAssetsManager.ALREADY_UP_TO_DATE:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode, am.getRemoteManifest().getVersion());
                        this.bundleStatusRecord[bundleKey].value.remoteVersion = am.getRemoteManifest().getVersion();
                        this.bundleStatusRecord[bundleKey].value.status = HasUpdateStatus.UpToDate;
                        this.bundleStatusRecord[bundleKey].again();
                        completed = true;
                        break;
                    case native.EventAssetsManager.UPDATE_PROGRESSION:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode, event.getPercent(), event.getPercentByFile());
                        break;
                    case native.EventAssetsManager.ASSET_UPDATED:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.ERROR_UPDATING:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.UPDATE_FINISHED:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        // search path
                        var searchPathsSet = new Set(native.fileUtils.getSearchPaths());
                        var newSearchPaths = Array.from(new Set(am.getLocalManifest().getSearchPaths()));
                        newSearchPaths.forEach(_ => { searchPathsSet.delete(_) });
                        searchPathsSet.delete(this.getSubgamePath());
                        let searchPaths = Array.from(searchPathsSet);
                        searchPaths.unshift(...newSearchPaths); // 保证新的资源优先加载
                        searchPaths.unshift(this.getSubgamePath());
                        localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
                        native.fileUtils.setSearchPaths(searchPaths);
                        if (bundleKey != null) {
                            // 安装过程
                            let tempPath = this.getStoragePath(bundleName) + "_temp/";
                            let baseOffset = tempPath.length;
                            let fileList: string[] = [];
                            if (native.fileUtils.isDirectoryExist(tempPath) && !native.fileUtils.isFileExist(tempPath + bundleName + '_project.manifest.temp')) {
                                native.fileUtils.listFilesRecursively(tempPath, fileList);
                                fileList.forEach(srcPath => {
                                    let relativePath = srcPath.substring(baseOffset);
                                    let dstPath = this.getStoragePath(bundleName) + '/' + relativePath;
                                    console.log('@', srcPath, '->', dstPath);
                                    let lastIdx = srcPath.lastIndexOf('/');
                                    if (lastIdx == srcPath.length - 1) {
                                        native.fileUtils.createDirectory(dstPath);
                                    } else {
                                        if (native.fileUtils.isFileExist(dstPath)) {
                                            native.fileUtils.removeFile(dstPath);
                                        }
                                        native.fileUtils.renameFile(srcPath, dstPath);
                                    }
                                });
                            }
                            native.fileUtils.removeDirectory(tempPath);
                        }
                        completed = true;
                        break;
                    case native.EventAssetsManager.UPDATE_FAILED:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    case native.EventAssetsManager.ERROR_DECOMPRESS:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                    default:
                        this.log('@event:', bundleName, HotUpdateManager.EventCodeToString(eventCode), eventCode);
                        break;
                }
                observer.next({ event });
                if (completed) observer.complete();
            });

            let state = am.getState();
            this.log('@state', state, HotUpdateManager.StateToString(state));

            if (justCheck) {
                am.checkUpdate();
            } else {
                am.update();
            }
        });
    }

    // 每个子包可更新状态
    // bundleStatusRecord = ReactiveProperty.Create<Record<string, HotUpdateStatus>>({});
    bundleStatusRecord: Record<string, ReactiveProperty<HotUpdateStatus>> = {};

    initBundleStatusRecord() {
        for (let bundleKey in { null: {}, ...SubgameConfig }) {
            this.bundleStatusRecord[bundleKey] = ReactiveProperty.Create<HotUpdateStatus>({ status: HasUpdateStatus.Unknown, localVersion: '', remoteVersion: '', percent: 0 });
        }
    }
}
globalThis.HotUpdateManager = HotUpdateManager;

export const hum = HotUpdateManager.instance = new HotUpdateManager();